package com.softserveinc.ita.kaiji.model.player.bot;

import com.softserveinc.ita.kaiji.model.Card;
import com.softserveinc.ita.kaiji.model.DeckImpl;
import com.softserveinc.ita.kaiji.model.player.Player;
import com.softserveinc.ita.kaiji.model.game.RoundResult;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Hard bot implementation
 * @author  Ievgen Sukhov
 * @since 19.03.14.
 * @version 1.3
 */
public class HardBotImpl extends Bot {

    //AI
    private int typeNum;
    private Player opponent;
    private int roundNumber;
    private List<RoundResult> roundResults;
    private int[][] markovCounters;
    private Map<Card, Integer> dict = new HashMap<>();
    private Boolean inited;

    public HardBotImpl() {}

    /**
     * @param cardNumber number of cards of each type in deck
     */
    protected HardBotImpl(Integer cardNumber) {
        this.deck = new DeckImpl(cardNumber);

        //AI pre-init
        typeNum = Card.values().length;
        markovCounters = new int[typeNum][typeNum];
        inited = false;

        this.canPlay = true;
        statusChanger.switchState(PlayerStatus.TURN_READY);
    }

    /**
     * Strategy is based on a simple Markov chain
     * <a href="http://en.wikipedia.org/wiki/Markov_chain">More info here</a>
     * @throws java.lang.IllegalStateException if no GameHistory is available for bot
     */
    @Override
    protected void executeStrategy() {
        if (this.gameData == null) {
            throw new IllegalStateException("No GameHistory is available, can not configure bot for hard difficulty");
        }

        if (!inited) {
            initAI();
        }

        Card oneBefore;
        Card twoBefore;

        roundNumber = roundResults.size();
        if (logger.isTraceEnabled()) logger.trace("Round results size:" + roundNumber);
        if (roundNumber < 2) {
            this.chosenCard = this.deck.getRandomCard();
            this.deck.pickCard(chosenCard);
        } else {
            oneBefore = roundResults.get(roundNumber - 1).getCard(opponent);
            twoBefore = roundResults.get(roundNumber - 2).getCard(opponent);
            if (logger.isTraceEnabled()) {
                logger.trace("Bot hard AI debug: onebefore-"
                        + oneBefore + "  twoBefore-" + twoBefore);
            }

            incMarkovCounter(oneBefore, twoBefore);

            // Get data array that corresponds to opponents latest move.
            int[] counts = markovCounters[dict.get(oneBefore)];
            // If next is rock -> move paper.
            if (counts[0] > counts[1] && counts[0] > counts[2]) {
                this.chosenCard = Card.PAPER;
                if (!this.deck.pickCard(chosenCard)) {
                    this.chosenCard = Card.ROCK;
                    if (!this.deck.pickCard(chosenCard)) {
                        this.chosenCard = Card.SCISSORS;
                        this.deck.pickCard(chosenCard);
                    }
                }
            }
            // paper -> scissors.
            else if (counts[1] > counts[2]) {

                this.chosenCard = Card.SCISSORS;
                if (!this.deck.pickCard(chosenCard)) {
                    this.chosenCard = Card.PAPER;
                    if (!this.deck.pickCard(chosenCard)) {
                        this.chosenCard = Card.ROCK;
                        this.deck.pickCard(chosenCard);
                    }
                }
            }
            // scissors -> rock.
            else   {   this.chosenCard = Card.ROCK;
                if (!this.deck.pickCard(chosenCard)) {
                    this.chosenCard = Card.SCISSORS;
                    if (!this.deck.pickCard(chosenCard)) {
                        this.chosenCard = Card.PAPER;
                        this.deck.pickCard(chosenCard);
                    }
                }
            }
        }

    }

    private void initAI() {
            if (this.gameData != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Initializing AI settings for bot");
                }

                Set<Player> players = gameData.getGameInfo().getPlayers();
                for (Player p : players) {
                    if (!p.isBot()) {
                        this.opponent = p;
                    }
                }

                roundResults = gameData.getRoundResults();
                for (int i = 0; i < typeNum; ++i) {
                    for (int j = 0; j < typeNum; ++j) {
                        markovCounters[i][j] = 0;
                    }
                }

                dict.put(Card.ROCK, 0);
                dict.put(Card.PAPER, 1);
                dict.put(Card.SCISSORS, 2);
                inited = true;
            } else if (this.gameData == null) {
                statusChanger.switchState(PlayerStatus.PLAYER_BROKEN);
                throw new IllegalStateException("No Game History is available, can not configure bot for hard difficulty");
            }
        }


    private void incMarkovCounter(Card oneBefore, Card twoBefore) {
        this.markovCounters[dict.get(twoBefore)][dict.get(oneBefore)]++;
    }
}
